<?php
/* --------------------------------------------------------------
   GDImageProcessor.php 2023-02-01
   Gambio GmbH
   http://www.gambio.de
   Copyright (c) 2023 Gambio GmbH
   Released under the GNU General Public License (Version 2)
   [http://www.gnu.org/licenses/gpl-2.0.html]
   --------------------------------------------------------------
*/

declare(strict_types=1);

namespace Gambio\Admin\Modules\ImageProcessing\App\Processor;

use Gambio\Admin\Modules\ImageProcessing\App\Data\Gd\ProcessedImageInformationRepository;
use Gambio\Admin\Modules\ImageProcessing\Exceptions\ImageNotFoundException;
use Gambio\Admin\Modules\ImageProcessing\Exceptions\ImageProcessingFailedException;
use Gambio\Admin\Modules\ImageProcessing\Exceptions\UnsupportedImageTypeException;
use Gambio\Admin\Modules\ImageProcessing\Model\Configuration\CustomImageConfiguration;
use Gambio\Admin\Modules\ImageProcessing\Model\Configuration\ImageConfiguration;
use Gambio\Admin\Modules\ImageProcessing\Model\ProcessedImageInformation;
use GdImage;

/**
 * Class GDImageProcessor
 *
 * @package Gambio\Admin\Modules\ImageProcessing\App\Processor
 */
class GDImageProcessor
{
    private const SUPPORTED_IMAGE_TYPES = [
        IMAGETYPE_PNG,
        IMAGETYPE_JPEG,
        IMAGETYPE_GIF,
        IMAGETYPE_WEBP,
    ];
    
    private ProcessedImageInformationRepository $repository;
    
    
    /**
     * GDImageProcessor constructor.
     *
     * @param ProcessedImageInformationRepository $repository
     */
    public function __construct(ProcessedImageInformationRepository $repository)
    {
        $this->repository = $repository;
    }
    
    
    /**
     * Processes the source image based on the image configuration.
     *
     * @param string             $source
     * @param string             $destination
     * @param ImageConfiguration $imageConfiguration
     *
     * @throws ImageProcessingFailedException
     * @throws UnsupportedImageTypeException
     * @throws ImageNotFoundException
     */
    public function process(string $source, string $destination, ImageConfiguration $imageConfiguration): void
    {
        $imageInformation = $this->repository->getProcessedImageInformation($source, $destination, $imageConfiguration);
        $this->processImage($source, $destination, $imageInformation);
    }
    
    
    /**
     * Custom image processing.
     *
     * Otherwise than `::process`, this method takes the ratio for the final image based on the
     * `CustomImageConfiguration` instead of calculating the ratio.
     *
     * @param string                   $source
     * @param string                   $destination
     * @param CustomImageConfiguration $imageConfiguration
     *
     * @throws ImageNotFoundException
     * @throws ImageProcessingFailedException
     * @throws UnsupportedImageTypeException
     * @see GDImageProcessor::process
     */
    public function processCustom(
        string                   $source,
        string                   $destination,
        CustomImageConfiguration $imageConfiguration
    ): void {
        $imageInformation = $this->repository->getCustomProcessedImageConfiguration($source,
                                                                                    $destination,
                                                                                    $imageConfiguration);
        $this->processImage($source, $destination, $imageInformation);
    }
    
    
    /**
     * Creates the processed image based on the image information.
     *
     * @param string                    $source
     * @param string                    $destination
     * @param ProcessedImageInformation $imageInformation
     *
     * @throws ImageProcessingFailedException
     * @throws UnsupportedImageTypeException
     */
    private function processImage(
        string                    $source,
        string                    $destination,
        ProcessedImageInformation $imageInformation
    ): void {
        $imageType = $imageInformation->imageType();
        
        if (!in_array($imageType, self::SUPPORTED_IMAGE_TYPES, true)) {
            $message = "Unsupported image type ($imageType). Supported types are png, jp[e]g, gif and webp";
            throw new UnsupportedImageTypeException($message);
        }
        
        $sourceImage = $this->getSourceImage($source, $imageInformation->imageType());
        $targetImage = imagecreatetruecolor($imageInformation->finalWidth(), $imageInformation->finalHeight());
        
        $this->fillPngsTransparent($imageType, $targetImage);
        $this->imageCopyResampled($targetImage, $sourceImage, $imageInformation);
        $this->saveImage($destination, $imageType, $sourceImage, $targetImage);
    }
    
    
    /**
     * Saves the processed image to the destination.
     *
     * @param string  $destination
     * @param int     $imageType
     * @param GdImage $sourceImage
     * @param GdImage $targetImage
     *
     * @throws ImageProcessingFailedException
     * @throws UnsupportedImageTypeException
     */
    private function saveImage(string $destination, int $imageType, GdImage $sourceImage, GdImage $targetImage): void
    {
        switch ($imageType) {
            case IMAGETYPE_JPEG:
                $result = imagejpeg($targetImage, $destination);
                break;
            case IMAGETYPE_PNG:
                $result = imagepng($targetImage, $destination);
                break;
            case IMAGETYPE_GIF:
                $result = imagegif($targetImage, $destination);
                break;
            case IMAGETYPE_WEBP:
                $result = imagewebp($targetImage, $destination);
                break;
            default:
                $message = "Unsupported image type ($imageType). Supported types are png, jp[e]g, gif and webp";
                throw new UnsupportedImageTypeException($message);
        }
        
        imagedestroy($sourceImage);
        imagedestroy($targetImage);
        
        if (!$result) {
            $message = "Image processing for target image '$destination' failed.";
            throw new ImageProcessingFailedException($message);
        }
    }
    
    
    /**
     * Fills png images with transparent background.
     *
     * @param int     $imageType
     * @param GdImage $targetImage
     */
    private function fillPngsTransparent(int $imageType, GdImage $targetImage): void
    {
        if ($imageType === IMAGETYPE_PNG) {
            imagealphablending($targetImage, false);
            imagesavealpha($targetImage, true);
            $transparent = imagecolorallocatealpha($targetImage, 255, 255, 255, 127);
            imagefill($targetImage, 0, 0, $transparent);
        }
    }
    
    
    /**
     * GD `imagecopyresampled` utility to save code space.
     *
     * @param GdImage                   $targetImage
     * @param GdImage                   $sourceImage
     * @param ProcessedImageInformation $imageInformation
     */
    private function imageCopyResampled(
        GdImage                   $targetImage,
        GdImage                   $sourceImage,
        ProcessedImageInformation $imageInformation
    ): void {
        imagecopyresampled($targetImage,
                           $sourceImage,
                           0,
                           0,
                           0,
                           0,
                           $imageInformation->finalWidth(),
                           $imageInformation->finalHeight(),
                           $imageInformation->imageWidth(),
                           $imageInformation->imageHeight());
    }
    
    
    /**
     * Creates the GD source image.
     *
     * @param string $source
     * @param int    $imageType
     *
     * @return GdImage|resource
     * @throws UnsupportedImageTypeException
     */
    private function getSourceImage(string $source, int $imageType)
    {
        switch ($imageType) {
            case IMAGETYPE_JPEG:
                $image = imagecreatefromjpeg($source);
                break;
            case IMAGETYPE_PNG:
                $image = imagecreatefrompng($source);
                break;
            case IMAGETYPE_GIF:
                $image = imagecreatefromgif($source);
                break;
            case IMAGETYPE_WEBP:
                $image = imagecreatefromwebp($source);
                break;
            default:
                $message = "Unsupported image type ($imageType). Supported types are png, jp[e]g, gif and webp";
                throw new UnsupportedImageTypeException($message);
        }
        if ($image === false) {
            $message = "Image creation failed (type: $imageType). Supported types are png, jp[e]g, gif and webp";
            throw new UnsupportedImageTypeException($message);
        }
        
        return $image;
    }
}